package pl.edu.agh.anomalyids.lerad.rule;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.tuple.Pair;

import pl.edu.agh.anomalyids.lerad.aspect.enums.AspectType;
import pl.edu.agh.anomalyids.lerad.aspect.enums.Match;
import pl.edu.agh.anomalyids.network.Packet;
import pl.edu.agh.anomalyids.network.Packet.Protocol;
import pl.edu.agh.anomalyids.network.header.Header;
import pl.edu.agh.anomalyids.network.header.impl.HTTPHeader;
import pl.edu.agh.anomalyids.network.header.impl.ICMPHeader;
import pl.edu.agh.anomalyids.network.header.impl.IPv4Header;
import pl.edu.agh.anomalyids.network.header.impl.TCPHeader;
import pl.edu.agh.anomalyids.network.header.impl.UDPHeader;

public class RulesMatcher {

	public static Pair<Match, List<Rule>> checkByAspect(AspectType aspectType,
			Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		if (aspectType.equals(AspectType.HTTPMETHOD)) {
			return checkHttp(packet, rulesMap);
		} else if (aspectType.equals(AspectType.SRCIP)) {
			return checkSrcIp(packet, rulesMap);
		} else if (aspectType.equals(AspectType.DESTIP)) {
			return checkDestIp(packet, rulesMap);
		} else if (aspectType.equals(AspectType.DESTPORT)) {
			return checkDestPort(packet, rulesMap);
		} else if (aspectType.equals(AspectType.SRCPORT)) {
			return checkSrcPort(packet, rulesMap);
		} else if (aspectType.equals(AspectType.TCPFLAG)) {
			return checkTcpFlag(packet, rulesMap);
		} else if (aspectType.equals(AspectType.TTL)) {
			return checkTtl(packet, rulesMap);
		} else if (aspectType.equals(AspectType.ICMPCODE)) {
			return checkIcmpCode(packet, rulesMap);
		} else if (aspectType.equals(AspectType.ICMPTYPE)) {
			return checkIcmpType(packet, rulesMap);
		} else {
			List<Rule> emptyList = new ArrayList<Rule>();
			return Pair.of(Match.UNKNOWN, emptyList);
		}
	}

	private static Pair<Match, List<Rule>> checkIcmpCode(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.ICMPCODE);
		Header h = packet.getProtocols().get(Protocol.ICMP);
		if (map != null && h != null) {
			ICMPHeader header = (ICMPHeader) h;
			HashSet<Rule> rules = map.get(header.getCode());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkIcmpType(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.ICMPCODE);
		Header h = packet.getProtocols().get(Protocol.ICMP);
		if (map != null && h != null) {
			ICMPHeader header = (ICMPHeader) h;
			HashSet<Rule> rules = map.get(header.getType());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkTtl(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.TTL);
		Header h = packet.getProtocols().get(Protocol.IPv4);
		if (map != null && h != null) {
			IPv4Header header = (IPv4Header) h;
			HashSet<Rule> rules = map.get(header.getTTL());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkTcpFlag(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.TCPFLAG);
		Header h = packet.getProtocols().get(Protocol.TCP);
		if (map != null && h != null) {
			TCPHeader header = (TCPHeader) h;
			HashSet<Rule> rules = map.get(header.getFlags());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkSrcPort(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.SRCPORT);
		Header h = packet.getProtocols().get(Protocol.TCP);
		if (map != null && h != null) {
			TCPHeader header = (TCPHeader) h;
			HashSet<Rule> rules = map.get(header.getSrcPort());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		} else {
			h = packet.getProtocols().get(Protocol.UDP);
			if (map != null && h != null) {
				UDPHeader header = (UDPHeader) h;
				HashSet<Rule> rules = map.get(header.getSrcPort());
				if (rules != null) {
					for (Rule rule : rules) {
						Match m = rule.validate(packet);
						if (m == Match.DIFFER) {
							result.add(rule);
						} else if (m == Match.SAME) {
							return Pair.of(m, result);
						}
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkDestPort(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.DESTPORT);
		Header h = packet.getProtocols().get(Protocol.TCP);
		if (map != null && h != null) {
			TCPHeader header = (TCPHeader) h;
			HashSet<Rule> rules = map.get(header.getDestPort());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		} else {
			h = packet.getProtocols().get(Protocol.UDP);
			if (map != null && h != null) {
				UDPHeader header = (UDPHeader) h;
				HashSet<Rule> rules = map.get(header.getDestPort());
				if (rules != null) {
					for (Rule rule : rules) {
						Match m = rule.validate(packet);
						if (m == Match.DIFFER) {
							result.add(rule);
						} else if (m == Match.SAME) {
							return Pair.of(m, result);
						}
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkDestIp(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.DESTIP);
		Header h = packet.getProtocols().get(Protocol.IPv4);
		if (map != null && h != null) {
			IPv4Header header = (IPv4Header) h;
			HashSet<Rule> rules = map.get(header.getDestAddress());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkSrcIp(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.SRCIP);
		Header h = packet.getProtocols().get(Protocol.IPv4);
		if (map != null && h != null) {
			IPv4Header header = (IPv4Header) h;
			HashSet<Rule> rules = map.get(header.getSrcAddress());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}

	private static Pair<Match, List<Rule>> checkHttp(Packet packet,
			HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap) {
		List<Rule> result = new ArrayList<Rule>();
		Map<Object, HashSet<Rule>> map = rulesMap.get(AspectType.HTTPMETHOD);
		Header h = packet.getProtocols().get(Protocol.HTTP);
		if (map != null && h != null) {
			HTTPHeader header = (HTTPHeader) h;
			HashSet<Rule> rules = map.get(header.getRequestMethod());
			if (rules != null) {
				for (Rule rule : rules) {
					Match m = rule.validate(packet);
					if (m == Match.DIFFER) {
						result.add(rule);
					} else if (m == Match.SAME) {
						return Pair.of(m, result);
					}
				}
			}
		}
		return Pair.of(Match.DIFFER, result);
	}
}
